//GA
//$Id: ga_common.h 175 2009-09-12 17:34:23Z Oleg.Bulychov $
#pragma once
#ifndef GA_COMMON_H
#define GA_COMMON_H

//__________________________________________________________________________________
#include "src/heo/include/selection.h"
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
template<class P, class S, class C>
class GA
{
public:
    typedef P TProblem;
    typedef S TSolution;
    typedef C TConfig;
    typedef typename S::TDirection TDirection;
    typedef typename S::TCostType  TCostType;
protected:
    TSolution** parent_;
    TSolution** offspring_;
    TSolution** common_;
    TSolution** migrant_list_;
    int parent_size_;
    int offspring_size_;
public:
    GA():
        parent_(NULL),
        offspring_(NULL),
        common_(NULL),
        migrant_list_(NULL),
        parent_size_(0),
        offspring_size_(0)
    {}

    void init(TProblem const& problem, TConfig const& cfg)
    {
        parent_ = new TSolution*[cfg.population_size_];
        offspring_ = new TSolution*[cfg.offspring_size_];
        parent_size_ = cfg.population_size_;
        offspring_size_ = cfg.offspring_size_;

        if (!cfg.only_offspring_)
            common_ = new TSolution*[cfg.population_size_ + cfg.offspring_size_];
        if (cfg.migration_size_)
            migrant_list_ = new TSolution*[cfg.migration_size_];
    }

    void generate_Random_Population(TProblem const& problem, TConfig const& cfg, uniform_random& rnd)
    {
        for (int i = 0; i < parent_size_; ++i)
        {
            parent_[i] = new TSolution;
            parent_[i]->init(problem, cfg);
            parent_[i]->generate_Random(cfg, rnd);
            if (common_)
                common_[i] = parent_[i];
        }
        for (int i = 0; i < offspring_size_; ++i)
        {
            offspring_[i] = new TSolution;
            offspring_[i]->init(problem, cfg);
            if (common_)
                common_[i + parent_size_] = offspring_[i];
        }
    }

    ~GA()
    {
        if (parent_)
        {
            for (int i = 0; i < parent_size_; ++i)
                delete parent_[i];
            delete [] parent_;
        }
        if (offspring_)
        {
            for (int i = 0; i < offspring_size_; ++i)
                delete offspring_[i];
            delete [] offspring_;
        }
        delete[] common_;
        delete[] migrant_list_;
    }

    void select_Parents(TConfig const& cfg, uniform_random& rnd)
    {
        //KProfiler pr("sel par");
        //TODO fix me: apply 'factory' pattern
        switch(cfg.parent_selection_)
        {
        case SELECTION_ROULETTE:
            {
                Selection_Roulette<TDirection> sel;
                sel.init(parent_, parent_size_);
                sel.select(offspring_, offspring_size_, parent_, parent_size_, cfg, rnd);
            }
            break;
        case SELECTION_TOURNAMENT:
            {
                Selection_Tournament<TDirection> sel;
                sel.init(parent_, parent_size_);
                sel.select(offspring_, offspring_size_, parent_, parent_size_, cfg, rnd);
            }
            break;
        default:
            {
                Selection_Random<TDirection> sel;
                sel.init(parent_, parent_size_);
                sel.select(offspring_, offspring_size_, parent_, parent_size_, cfg, rnd);
            }
            break;
        }
    }

    void select_Offsprings(TConfig const& cfg, uniform_random& rnd)
    {
        //KProfiler pr("sel offs");
        TSolution** src = offspring_;
        int src_size = offspring_size_;
        if (common_)
        {
            src = common_;
            src_size += parent_size_;
        }
        //TODO fix me: apply 'factory' pattern
        switch(cfg.offspring_selection_)
        {
        case SELECTION_ROULETTE:
            {
                Selection_Roulette<TDirection> sel;
                sel.init(src, src_size);
                sel.select(parent_, parent_size_, src, src_size, cfg, rnd);
            }
            break;
        case SELECTION_TOURNAMENT:
            {
                Selection_Tournament<TDirection> sel;
                sel.init(src, src_size);
                sel.select(parent_, parent_size_, src, src_size, cfg, rnd);
            }
            break;
        default:
            {
                Selection_Random<TDirection> sel;
                sel.init(src, src_size);
                sel.select(parent_, parent_size_, src, src_size, cfg, rnd);
            }
            break;
        }
    }

    void evaluate_Parents(TProblem const& problem)
    {
        for (int i = 0; i < parent_size_; ++i)
        {
            parent_[i]->evaluate(problem);
        }
    }

    void evaluate_Offsprings(TProblem const& problem)
    {
        for (int i = 0; i < offspring_size_; ++i)
        {
            offspring_[i]->evaluate(problem);
        }
    }

    void crossover(TConfig const& cfg, uniform_random& rnd)
    {
        //KProfiler pr("cross");
        for (int i = 0; i < offspring_size_ - 1; i += 2)
        {
            if (rnd.next_Double() < cfg.crossover_probability_)
                offspring_[i]->cross(*offspring_[i + 1], rnd);
        }
    }

    void mutation(double mutationProbability, uniform_random& rnd)
    {
        //KProfiler pr("mutate");
        for (int i = 0; i < offspring_size_; ++i)
        {
            offspring_[i]->mutate(mutationProbability, rnd);
        }
    }

    void update_Solution(TSolution& sol, TConfig const& cfg, int step, int rank)
    {
        TCostType fitness = parent_[0]->get_Fitness();
        TCostType best = 0;
        for (int i = 1; i < parent_size_; ++i)
        {
            TCostType f =  parent_[i]->get_Fitness();
            if (TDirection()(f, fitness))
            {
                fitness = f;
                best = i;
            }
        }
        if (TDirection()(fitness, sol.get_Fitness()))
        {
            sol.assign(*parent_[best]);
        }
        if (rank >= cfg.debug_level_)
            return;

        static const TCostType bad = ((int)TDirection::direction == OPT_MAXIMIZE) ?
            std::numeric_limits<TCostType>::min() : std::numeric_limits<TCostType>::max();
        static TCostType prev = bad;
        if (prev != bad)
        {
            if (TDirection()(prev, fitness))
            {
                std::cout << std::setw(2) << rank << ":" << std::setw(5) << step << "<<<WORSE: " << fitness <<
                    " -" << abs(fitness - prev) << std::endl;
            }
            else if (TDirection()(fitness, prev))
            {
                std::cout << std::setw(2) << rank << ":" << std::setw(5) << step << ">>>BETTER: " << fitness <<
                    " +" << abs(fitness - prev) << std::endl;
                prev = fitness;
            }
            else
            {
                std::cout << std::setw(2) << rank << ":" << std::setw(5) << step << "===SAME: " << fitness << std::endl;
            }
        }
        else
            prev = fitness;
    }

    template<class Send>
    void emigrate(Send& out, TConfig const& cfg, uniform_random& rnd)
    {
        //:_sleep(100);
        //TODO fix me: apply 'factory' pattern
        Selection_Random<TDirection> sel;
        sel.init(parent_, parent_size_);
        for (int i = 0; i < cfg.migration_size_; ++i)
        {
            TSolution* pt = sel.select_One(parent_, parent_size_, cfg, rnd);
            out << *pt;
        }
    }

    template<class Recv>
    void immigrate(Recv& in, TSolution& tmp, TConfig const& cfg, uniform_random& rnd)
    {
        //TODO fix me: apply 'factory' pattern
        {
            Selection_Random<TDirection> sel;
            sel.init(parent_, parent_size_);
            for (int i = 0; i < cfg.migration_size_; ++i)
            {
                TSolution* pt = sel.select_One(parent_, parent_size_, cfg, rnd);
                migrant_list_[i] = pt;
            }
        }
        //:additional migrant_list_ is used becoz
        //:sel may be invalid after pt->assign
        for (int i = 0; i < cfg.migration_size_; ++i)
        {
            in >> tmp;
            TSolution* pt = migrant_list_[i];
            if (TDirection()(tmp.get_Fitness(), pt->get_Fitness()))
                pt->assign(tmp);
        }
    }
};
//__________________________________________________________________________________
template<class Recv, class Send, class ISend>
class ring_migration
{
public:
    template<class GA, class TSolution, class TConfig, class Comm>
    static void migrate(GA& ga, int rank, int prev, int next, int num_proc, TSolution& tmp,
        TConfig const& cfg, uniform_random& rnd, int step, Comm& comm)
    {
        if (rank == prev || cfg.migration_rate_ <= 0 || cfg.migration_size_ <= 0)
            return;
        Recv in(prev, comm);
        if (cfg.async_mode_)
        {
            ISend out(next, comm);
            migrate_Async(ga, rank, num_proc, tmp, cfg, rnd, step, in, out);
        }
        else
        {
            Send out(next, comm);
            migrate_Sync(ga, rank, num_proc, tmp, cfg, rnd, step, in, out);
        }
    }

    template<class GA, class TSolution, class TConfig>
    static void migrate_Sync(GA& ga, int rank, int num_proc, TSolution& tmp,
        TConfig const& cfg, uniform_random& rnd, int step, Recv& in, Send& out)
    {
        if (step % cfg.migration_rate_)
            return;
        if (num_proc & 1)
        {//:odd - slow
            if (0 == rank)
                ga.emigrate(out, cfg, rnd);

            ga.immigrate(in, tmp, cfg, rnd);

            if (0 != rank)
                ga.emigrate(out, cfg, rnd);
        }
        else
        {//:even - faster
            if (rank & 1)
            {
                //std::cout << rank << ":" << step << ":emigration" << std::endl;
                ga.emigrate(out, cfg, rnd);
                ga.immigrate(in, tmp, cfg, rnd);
            }
            else
            {
                ga.immigrate(in, tmp, cfg, rnd);
                ga.emigrate(out, cfg, rnd);
            }
        }
    }

    template<class GA, class TSolution, class TConfig>
    static void migrate_Async(GA& ga, int rank, int num_proc, TSolution& tmp,
        TConfig const& cfg, uniform_random& rnd, int step, Recv& in, ISend& out)
    {
        if ((step % cfg.migration_rate_) == 0)
        {
            if (0 == rank)
            {//:start the 'stadium' wave
                ga.emigrate(out, cfg, rnd);
            }
        }
        if (!in.is_Data_Available())
            return;
        //std::cout << rank << ":" << step << ":***migrant(s) accepted" << std::endl;
        ga.immigrate(in, tmp, cfg, rnd);
        if (0 != rank)
        {//:continue wave
            ga.emigrate(out, cfg, rnd);
        }
    }
};
//__________________________________________________________________________________

#endif
//EOF!
